# encoding: utf-8

module TJP
    class Robot
        attr_reader :tjplurker
        
        # :call-seq: new(tjplurker)
        #            new(key, screte)
        #            new(key, secret, access_token, access_token_screte)
        # key:: consumer key
        # secret:: consumer secret
        def initialize *params
            case params.size
                when 1
                    @tjplurker = params[0]
                when 2
                    key, screte = params
                    @tjplurker = TJPlurker.new(key, screte)
                when 4
                    key, secret, access_token, access_token_screte = params
                    @tjplurker = TJPlurker.new(key, secret, access_token, access_token_screte)
            end
            @services = []
        end
        
        # Starting a loop and invokes services when a notification was listened.
        def start
            $tjpLog.info(self.class){"Start robot."}
            Thread.new{
                loop{
                    begin
                    @tjplurker.add_all_as_friends
                    sleep(60)
                    rescue => ex
                    end
                }
            }
            loop{
                @tjplurker.comet_channel{|data|
                    @services.each{|service|
                        begin
                            service.serve(@tjplurker, data)
                        rescue => ex
                            $tjpLog.error(self.class){"An error occured in #{service.class}, class: #{ex.class}, message: #{ex.message}, backtrace: #{ex.backtrace}"}
                        end
                    }
                }
            }
        end
        
        # service:: Can be instance TJPlurker::Service or class TJPlurker::Service or it's descendant classes.
        def add_service service
            if service.is_a? Service
                @services << service
                $tjpLog.info(self.class){"Add service: #{service.name}"}
            elsif service <= Service
                tmp = service.new
                @services << tmp
                $tjpLog.info(self.class){"Add service: #{tmp.name}"}
            else
                $tjpLog.warn(self.class){"Unrecognized service, ignored."}
            end
        end
    end

    class Service
        attr_reader :name
        def initialize name=self.class.to_s, &proc
            @name = name
            if block_given?
                @serve_proc = proc
            end
        end
        
        # Invoke when listened a new plurk.
        # data:: notification data
        def decide tjplurker, data
            true
        end
        
        # Invoke when decide return true
        # data:: notification data
        def action tjplurker, data
            if @serve_proc
                @serve_proc[tjplurker, data]
            else
                p data
            end
        end
        
        # It has higher priority than decide and action
        # data:: notification data
        def serve tjplurker, data
            action(tjplurker, data) if decide(tjplurker, data)
        end
    end

    class ThreadService < Service
        # lasting:: How long in second will a thread lives. If one plurk just be responsed,
        #           then it will live another +lasting+ time.
        # refresh:: Interval that checks if thread should terminate.
        def initialize name=self.class.to_s, lasting=60, refresh=1, &proc
            super name
            @lasting = lasting
            @refresh = refresh
            @plurk_id_hash = Hash.new
            @mutex = Mutex.new
        end
        
        # Invoke after thread termination.
        # You can use this to tell users this service just terminate.
        # data:: notification data
        def final tjplurker, data
        
        end
        
        def serve tjplurker, data
            if data.type=='new_plurk' && decide(tjplurker, data)
                @plurk_id_hash[data.plurk_id] = Thread.new(data.plurk_id){|plurk_id|
                    $tjpLog.debug(self.class){"Create thread in plurk ##{plurk_id}"}
                    Thread.current[:last_time] = Time.now.to_i
                    loop{
                        sleep(@refresh)
                        $tjpLog.debug(self.class){"Plurk ##{plurk_id} remain #{Thread.current[:last_time]+@lasting-Time.now.to_i} seconds."}
                        @mutex.lock
                        if Time.now.to_i - Thread.current[:last_time] > @lasting
                            @mutex.unlock
                            break
                        end
                        @mutex.unlock
                    }
                    $tjpLog.debug(self.class){"Terminate thread in plurk ##{plurk_id}"}
                    @mutex.lock
                    @plurk_id_hash.delete(plurk_id)
                    @mutex.unlock
                    final(tjplurker, data)
                }
            else
                @mutex.lock
                if data.type=='new_response' && @plurk_id_hash.has_key?(data.plurk_id)
                    @plurk_id_hash[data.plurk_id][:last_time] = Time.now.to_i
                    action(tjplurker, data)
                end
                @mutex.unlock
            end
        end
    end
end